#include <windows.h>
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

enum { AC, fortitude, reflex, will };

struct attack_data {

     const char * name;
     const char * key; // key if under player control
     int ammo; // how many attacks if limited, or -1 if unlimited
     int target_defence;
     int bonus;
     int damage_bonus;
     int damage_dice;
};

attack_data player_attacks[] = {

     //name         key ammo defence  mod dmg dice
     { "shoot",     "s",  6, reflex,   5,  10,  10 },
     { "punch",     "p", -1, AC,      15,   3,   4 },
     { "kick",      "k", -1, AC,      14,   4,   4 },
     { "headbutt",  "h", -1, reflex,  10,  10,  10 },
};

attack_data dragon_attacks[] = {

     //name     key ammo defence  mod dmg dice
     { "burn",  "b",  1, reflex,   8,  10,  10 },
     { "claw",  "c", -1, AC,       8,   5,  10 },
};

struct character {

     int initiative;
     int hit_points;
     int defences[4];
     attack_data * attacks;
     int number_of_attacks;
     int (*choose_attack)(character * attacker, character * defender);
     const char * name;
};

int roll(int sides)
{
     return rand() % sides + 1;
}

int roll_initiative(character * combatant)
{
     return roll(20) + combatant->initiative;
}

char get_keypress()
{
     char answer;
     cin >> answer; // wait for keypress to end
     return answer;
}

void print_attack_option(attack_data * attack, const char * defender)
{
     cout << "[" << attack->key << "] ";
     cout << attack->name << " the " << defender << "," << endl;
}

void list_attack_options(character * attacker, const char * defender)
{
     for (int i = 0; i < attacker->number_of_attacks; ++i) {

          print_attack_option(&attacker->attacks[i], defender);
     }
}

int do_attack(attack_data * attack, character * defender)
{
     int damage = 0;
     if (roll(20) + attack->bonus > defender->defences[attack->target_defence]) {

          damage = roll(attack->damage_dice) + attack->damage_bonus;
          defender->hit_points -= damage;
     }

     if (attack->ammo > 0) {

          attack->ammo--;
     }

     return damage;
}

void disable_input_buffering()
{
     HANDLE console = GetStdHandle(STD_INPUT_HANDLE);
     DWORD mode;
     GetConsoleMode(console, &mode);
     SetConsoleMode(console, mode & ~ENABLE_LINE_INPUT);
     cin >> noskipws;
}

void initialise_random_number_generator()
{
     srand((int)time(0));
}

void announce(const char * enemy_type)
{
     cout << "A " << enemy_type << " appears!" << endl;
     cout << endl;
}

void announce_turn(const char * name)
{
     cout << "It's the " << name << "'s turn." << endl;
}

char get_attack_key(attack_data * attack)
{
     return attack->key[0];
}

bool attack_needs_ammo(attack_data * attack)
{
     return attack->ammo != -1;
}

bool attack_has_ammo(attack_data * attack)
{
     return attack->ammo > 0;
}

bool can_use_attack(attack_data * attack)
{
     return !attack_needs_ammo(attack) || attack_has_ammo(attack);
}

int choose_player_attack(character * attacker, character * defender)
{
     int choice = -1;
     char answer;

     do {
          cout << "Will you:" << endl;
          list_attack_options(attacker, defender->name);
          cout << "[r] run away." << endl;
          cout << endl;
          cout << ">";

          answer = get_keypress();

          for (int i = 0; i < attacker->number_of_attacks; ++i) {

               if (answer == get_attack_key(&attacker->attacks[i])) {

                    if (can_use_attack(&attacker->attacks[i])) {

                         cout << endl;
                         cout << endl;
                         cout << "You attempt to " << attacker->attacks[i].name << " the " << defender->name << "..." << endl;
                         choice = i;

                    } else {

                         cout << endl;
                         cout << endl;
                         cout << "You are out of ammo!" << endl;
                         answer = '?';
                    }
               }
          }

          if (choice == -1) {

               switch (answer) {

               case 'r':
                    cout << endl;
                    cout << endl;
                    break;

               default:
                    cout << endl;
                    cout << endl;
                    cout << "I didn't understand that." << endl;
                    cout << endl;
                    answer = '?';
                    break;
               }
          }

     } while (answer == '?');

     return choice;
}

int choose_AI_attack(character * attacker, character * defender)
{
     int choice;
     do {
          choice = rand() % attacker->number_of_attacks;

          if (can_use_attack(&attacker->attacks[choice])) {

               cout << "The " << attacker->name << " tries to ";
               cout << attacker->attacks[choice].name << " the ";
               cout << defender->name << "..." << endl;

          } else {

               choice = -1;
          }

     } while (choice == -1);

     return choice;
}

void initialise_player(character * player)
{
     player->initiative = 4;
     player->hit_points = 20;
     player->defences[AC] = 20;
     player->defences[fortitude] = 12;
     player->defences[reflex] = 18;
     player->defences[will] = 15;
     player->attacks = player_attacks;
     player->number_of_attacks = sizeof(player_attacks) / sizeof(player_attacks[0]);
     player->choose_attack = choose_player_attack;
     player->name = "player";
}

void initialise_dragon(character * dragon)
{
     dragon->hit_points = 20;
     dragon->initiative = 8;
     dragon->defences[AC] = 20;
     dragon->defences[fortitude] = 20;
     dragon->defences[reflex] = 12;
     dragon->defences[will] = 15;
     dragon->attacks = dragon_attacks;
     dragon->number_of_attacks = 2;
     dragon->choose_attack = choose_AI_attack;
     dragon->name = "dragon";
}

bool process_combat_turn(character * attacker, character * defender)
{
     bool flee = false;
     int chosen_attack = -1; // no chosen_attack (flee instead)
     announce_turn(attacker->name);
     chosen_attack = attacker->choose_attack(attacker, defender);

     if (chosen_attack != -1) {

          int damage = do_attack(&(attacker->attacks[chosen_attack]), defender);
          if (damage > 0) {

               cout << "a hit for " << damage << " points of damage." << endl;

          } else {

               cout << "miss." << endl;
          }

     } else {

          flee = true;
     }

     return flee;
}


int main()
{
     disable_input_buffering();
     initialise_random_number_generator();

     character player;
     character dragon;
     initialise_player(&player);
     initialise_dragon(&dragon);

     character * enemy = &dragon;

     announce(enemy->name);

     character * attacker = enemy;
     character * defender = &player;

     if (roll_initiative(&player) > roll_initiative(enemy)) {

          swap(attacker, defender);
     }

     bool flee = false;
     do {

          flee = process_combat_turn(attacker, defender);
          swap(attacker, defender);

     } while (attacker->hit_points > 0 && defender->hit_points > 0 && !flee);

     if (player.hit_points < 1) {

          cout << "You died!" << endl;

     } else if (enemy->hit_points < 1) {

          cout << "You defeated the " << enemy->name << "!" << endl;

     } else {

          cout << "You live to fight another day..." << endl;
     }

     get_keypress();

     return 0;
}
